﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace UCL.Powersource.Helpers
{


    /// <summary>
    /// Class ensure stopping of propagation in <see cref="UCL.Powersource.Helpers.SelectableConfiguration"/>
    /// Monitor is always created when _SelectedItemsFromListBox_CollectionChanged is called
    /// After that children when containing items raise PropertyChanged event, it will be ignored here
    /// </summary>
    public class FreezeChildEventsMonitor : IDisposableEx
    {
        internal readonly object _MasterEvent_sender;
        internal readonly System.Collections.Specialized.NotifyCollectionChangedEventArgs _MasterEvent_e;

        internal FreezeChildEventsMonitor(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            _MasterEvent_e = e;
            _MasterEvent_sender = sender;
        }

        public bool IsDisposed
        {
            get;
            private set;
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private void Dispose(bool p)
        {
            if (p && !IsDisposed)
            {
                IsDisposed = true;
            }
        }
    }

    public abstract class ConfigurationCollection<T> : ObservableCollection<ConfigurationItem<T>>
        where T : class, ILabel
    {

    }


    public class SelectableConfiguration<T> : ObservableCollection<SelectableConfigurationItem<T>>,
        ISynchronizedCollection
        where T : class, ILabel
    {

        /// <summary>
        /// private field which contains pointer to Log, it's set on first access to Log property
        /// This file should not be accessed anywhere but Log getter
        /// </summary>
        private ILog _NT_Log;
        public ILog Log
        {
            get
            {
                return _NT_Log ?? (_NT_Log = LogManager.GetLog(typeof(SelectableConfiguration<T>)));
            }
        }

        public SelectableConfiguration()
        {
            SelectedItemsCollection = new SelectedCollection<T>(this);
        }

       

        public System.Collections.Generic.IEnumerator<T> GetSelected()
        {
            foreach (var it in this)
            {
                if (it.IsSelected) yield return it.ItemToConfigure;
            }
        }

        [DataMember]
        public SelectedCollection<T> SelectedItemsCollection
        {
            get;
            private set;
        }

        protected override void InsertItem(int index, SelectableConfigurationItem<T> item)
        {
            if (item.ItemToConfigure == null) throw new InvalidOperationException(string.Format("Ubale to add item {1} to {0} unless it already contains ItemToConfigure", this.ToString(), item.ToString()));
            PropertyChangedEventManager.AddHandler(item, new EventHandler<PropertyChangedEventArgs>(SelectableConfigurationItem_PropertyChanged), string.Empty);
            base.InsertItem(index, item);
            if (item.IsSelected && SelectedItemsCollection.Contains(item.ItemToConfigure))
            {
                SynchronizeSelectedItemsCollectionsWithItem(item);
            }
        }

        private static string Item_IsSelected_PropertyName;
        private static void EnsureItemPropertyNamesKnown(SelectableConfigurationItem<T> item)
        {
            if (string.IsNullOrWhiteSpace(Item_IsSelected_PropertyName)) Item_IsSelected_PropertyName = PropertyExtensions.GetPropertyName(() => item.IsSelected);
        }

        private void SelectableConfigurationItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var item = sender as SelectableConfigurationItem<T>;
            EnsureItemPropertyNamesKnown(item);
            if (e.PropertyName == Item_IsSelected_PropertyName)
            {
                OnItemIsSelectedChanged(item);
            }

        }

        private void OnItemIsSelectedChanged(SelectableConfigurationItem<T> item)
        {
            // Ignoring child events because we are in OnItemIsSelectedChanged
            if (HasFreezedChildEvents)
            {
                Log.DebugMethod("Debug: HasFreezedChildEvents==true - Ignoring change event on {0}", item.ToString());
                return;
            }
            else
            {
                SynchronizeSelectedItemsCollectionsWithItem(item);
            }
        }

        private void SynchronizeSelectedItemsCollectionsWithItem(SelectableConfigurationItem<T> item)
        {
            if (SelectedItemsCollection != null && item.ItemToConfigure != null)
            {
                var inSelected = SelectedItemsCollection.Contains(item.ItemToConfigure);
                if (inSelected && !item.IsSelected)
                {
                    var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new T[] { item.ItemToConfigure });
                    SelectedItemsCollection.RaiseCollectionChange(this, e);
                }
                else
                    if (!inSelected && item.IsSelected)
                    {
                        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new T[] { item.ItemToConfigure });
                        SelectedItemsCollection.RaiseCollectionChange(this, e);
                    }
                    else
                    {
                        Log.Warn("Item {0} already synchronized with SelectedItemsCollection from {1}", item.ToString(), this.ToString());
                    }
            }
            if (_SelectedItemsFromListBox == null)
            {
                Log.DebugMethod("_SelectedItemsFromListBox == null  - Ignoring change event on {0}", item.ToString());
            }
            else
            {
                var SelItems = (IList)_SelectedItemsFromListBox;
                if (item.IsSelected && !SelItems.Contains(item))
                {
                    Log.DebugMethod("Adding {0} to selected", item.ToString());
                    SelItems.Add(item);
                }
                else
                    if (!item.IsSelected && SelItems.Contains(item))
                    {
                        Log.DebugMethod("Removing {0} from selected", item.ToString());
                        SelItems.Remove(item);
                    }
            }
        }

        public void Add(T itemToConfigure, bool isSelected = false)
        {
            var it = this.FirstOrDefault(a => itemToConfigure.Equals(a.ItemToConfigure));
            if (it == null)
            {
                it = new SelectableConfigurationItem<T>(itemToConfigure) { IsSelected = isSelected };
                this.Add(it);
            }
        }

        public T Remove(T itemToConfigure)
        {
            var it = this.FirstOrDefault(a => itemToConfigure.Equals(a.ItemToConfigure));
            if (it == null) return default(T);
            else
            {
                this.Remove(it);
                return it.ItemToConfigure;
            }
        }



        private FreezeChildEventsMonitor _FreezeChildEventsMonitor;
        private bool HasFreezedChildEvents { get { return _FreezeChildEventsMonitor != null && !_FreezeChildEventsMonitor.IsDisposed; } }
        private FreezeChildEventsMonitor FreezeChildEvents(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (_FreezeChildEventsMonitor != null && !_FreezeChildEventsMonitor.IsDisposed)
            {
                var oldSender = _FreezeChildEventsMonitor._MasterEvent_sender;
                var oldSenderString = "#NULL";
                var newSenderString = "#NULL";
                if (oldSender != null)
                {
                    oldSenderString = string.Format("{0}[{1}]", oldSender.GetType().Name, oldSender.GetHashCode());
                }
                if (sender != null)
                {
                    newSenderString = string.Format("{0}[{1}]", sender.GetType().Name, sender.GetHashCode());
                }
                throw new InvalidOperationException(string.Format("Cannot perform FreezeChildEvents as they are already freezed. Original call {0}-{1}. New call {2}-{3}.", oldSenderString, _FreezeChildEventsMonitor._MasterEvent_e.Action, sender, e.Action));
            }
            _FreezeChildEventsMonitor = new FreezeChildEventsMonitor(sender, e);
            return _FreezeChildEventsMonitor;
        }




        void _SelectedItemsFromListBox_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            using (this.FreezeChildEvents(sender, e))
            {
                switch (e.Action)
                {
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                        if (e.NewItems != null)
                        {
                            foreach (var newItemObject in e.NewItems)
                            {
                                var newItem = newItemObject as SelectableConfigurationItem<T>;
                                if (newItem == null)
                                {
                                    Log.Warn("_SelectedItemsFromListBox_CollectionChanged removedItemObject ignored! removedItemObject is {0} - and it must be {1}.", newItemObject.GetType().Name, typeof(SelectableConfigurationItem<T>).Name);
                                    continue;
                                }
                                if (!this.Contains(newItem))
                                {
                                    Log.Warn("_SelectedItemsFromListBox_CollectionChanged removedItem {0}[{1}] ignored! It does not belong to this selectedItemsCollection {2}", newItem.Label, newItem.GetType().Name, this.GetType().Name);
                                    continue;
                                }
                                if (!newItem.IsSelected) newItem.IsSelected = true;
                                var r = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new T[] { newItem.ItemToConfigure });
                                SelectedItemsCollection.RaiseCollectionChange(this, r);
                            }
                        }
                        break;

                    case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                        if (e.OldItems != null)
                        {
                            foreach (var removedItemObject in e.OldItems)
                            {
                                var removedItem = removedItemObject as SelectableConfigurationItem<T>;
                                if (removedItem == null)
                                {
                                    Log.Warn("_SelectedItemsFromListBox_CollectionChanged removedItemObject ignored! removedItemObject is {0} - and it must be {1}.", removedItemObject.GetType().Name, typeof(SelectableConfigurationItem<T>).Name);
                                    continue;
                                }
                                if (!this.Contains(removedItem))
                                {
                                    Log.Warn("_SelectedItemsFromListBox_CollectionChanged removedItem {0}[{1}] ignored! It does not belong to this selectedItemsCollection {2}", removedItem.Label, removedItem.GetType().Name, this.GetType().Name);
                                    continue;
                                }
                                if (removedItem.IsSelected) removedItem.IsSelected = false;
                                var r = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new T[] { removedItem.ItemToConfigure });
                                SelectedItemsCollection.RaiseCollectionChange(this, r);
                            }
                        }
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                        //Move is ignored
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                        //We do not care about replace - not possible
                        Log.Error(null, "_SelectedItemsFromListBox_CollectionChanged ignored for Replace operation!");
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                        SynchronizeSelectedItems(true);
                        //var selectedItems = sender as IList<T>;

                        //if (selectedItems == null)
                        //{
                        //    Log.Warn("_SelectedItemsFromListBox_CollectionChanged ignored for Reset operation because sender is not {0} but {1}!", typeof(IList<T>).Name,sender.GetType().Name);
                        //}
                        //foreach (var it in this)
                        //{
                        //    it.IsSelected = selectedItems.Contains(it.ItemToConfigure);
                        //}

                        break;
                    default:
                        break;
                }
            }
        }

        private void SynchronizeSelectedItems(bool LoadSelectedFromSelectedItems = false)
        {
            if (_SelectedItemsFromListBox == null) return;
            var selItems = (IList)_SelectedItemsFromListBox;
            foreach (var it in this)
            {
                if (LoadSelectedFromSelectedItems)
                {
                    it.IsSelected = selItems.Contains(it);
                }
                else
                {
                    if (it.IsSelected && !selItems.Contains(it)) selItems.Add(it);
                    else
                        if (!it.IsSelected && selItems.Contains(it)) selItems.Remove(it);
                        else
                            Log.DebugMethod("No change for {0}", it.ToString());
                }
            }
            var encc = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            SelectedItemsCollection.RaiseCollectionChange(this, encc);
        }

        //private Dictionary<WeakReference<INotifyCollectionChanged>,EventHandler<NotifyCollectionChangedEventArgs> HandlersCollection;

        private INotifyCollectionChanged _SelectedItemsFromListBox;
        private EventHandler<NotifyCollectionChangedEventArgs> _SelectedItems_CollectionChanged_Handler;
        public void AttachToSelectedItemsCollection(IList incc)
        {
            if (AttachedToSelectedItemsCollection)
            {
                if (incc == _SelectedItemsFromListBox) Log.DebugMethod("Ignoring request. Already attached to this collection");
            }
            if (_SelectedItemsFromListBox != null) throw new NotSupportedException("Current implementation supports to only one INotifyCollectionChanged collection (usually that means one listbox SelectedItemsCollection).");
            _SelectedItemsFromListBox = incc as INotifyCollectionChanged;
            if (_SelectedItemsFromListBox == null)
            {
                throw new InvalidOperationException(string.Format("IList incc={0} does not implement {1}. Unable to perform AttachToSelectedItemsCollection.", incc, typeof(INotifyCollectionChanged)));
            }
            SynchronizeSelectedItems(false);
            _SelectedItems_CollectionChanged_Handler = new EventHandler<NotifyCollectionChangedEventArgs>(_SelectedItemsFromListBox_CollectionChanged);
            AttachedToSelectedItemsCollection = true;
            CollectionChangedEventManager.AddHandler(_SelectedItemsFromListBox, _SelectedItems_CollectionChanged_Handler);

        }

        bool AttachedToSelectedItemsCollection = false;

        public void DetachFromSelectedItemsCollection()
        {
            if (!AttachedToSelectedItemsCollection) return;
            if (_SelectedItemsFromListBox == null)
            {
                Log.Warn("_SelectedItemsFromListBox == null - Cannot detach not existing handler from INotifyCollectionChanged");
                return;
            }

            if (_SelectedItems_CollectionChanged_Handler == null)
            {
                Log.Warn("_SelectedItems_CollectionChanged_Handler == null - Cannot detach not existing  handler from INotifyCollectionChanged");
                return;
            }
            if (
                (_SelectedItemsFromListBox != null && _SelectedItems_CollectionChanged_Handler == null)
                ||
                (_SelectedItemsFromListBox == null && _SelectedItems_CollectionChanged_Handler != null)

                ) throw new InvalidOperationException(string.Format("Internal error. Only one of _SelectedItemsFromListBox and  _SelectedItems_CollectionChanged_Handler exists. - _SelectedItemsFromListBox={0} and _SelectedItems_CollectionChanged_Handler={1} ", _SelectedItemsFromListBox, _SelectedItems_CollectionChanged_Handler));

            CollectionChangedEventManager.RemoveHandler(_SelectedItemsFromListBox, _SelectedItems_CollectionChanged_Handler);
            _SelectedItemsFromListBox = null;
            _SelectedItems_CollectionChanged_Handler = null;
            AttachedToSelectedItemsCollection = false;
        }


    }
}
